/* 
 * File:   mpi_ub6.cpp
 * Author: sergey
 *
 * Created on 14 Декабрь 2009 г., 19:05
 *
 * Эта программа используется для запуска на кластере с использованием
 * OpenMPI. Она будет перебирать необходимые функции сложности не меньше
 * MinComplication.
 */

#include "DefineConfiguration.h"
#include "LPLibrary.h"
#include "LibraryInterface.h"
#include "NLLibrary.h"
#include <iostream>
#include <sstream>
#include <mpi/mpi.h>
#include <vector>
#include <signal.h>
#include <unistd.h>
#include <fstream>
#include <omp.h>

const UInt8 f1Complication = 7;
const UInt8 f2Complication = 8;
const UInt8 fComplication = 13;
const UInt32 n = 5;
typedef LibraryInfo<n> CurLibInfo;

int rank = 0, size = 1;

int main(int argc, char** argv) {
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD,&rank);
    MPI_Comm_size(MPI_COMM_WORLD,&size);

    std::ostringstream filename;

    MPI_Status state;

    if (rank == 0) {
        std::ofstream resultFile;
        resultFile.open("result.bin", std::ios::out | std::ios::binary);
        
        std::cout << "Starting task generator." << std::endl;
        filename.str("");
        filename << LIBRARYDIRECTORIES << "/lpblibs/lplibrary_" << CurLibInfo::n << ".bin";
        LPLibrary<CurLibInfo> lplib;
        lplib.load(filename.str());
        std::vector<UInt64> funcs;
        funcs.resize(size - 1);
        UInt64 busy = 1;                
       
        for(std::map<UInt64, UInt8>::iterator i = lplib.bfclass.begin(); i != lplib.bfclass.end(); i++)            
            if (i->second == f1Complication) {
                if (busy == size) {
                    UInt64 num;
                    std::vector<UInt64> res;
                    MPI_Recv(&num, 1, MPI_UNSIGNED_LONG_LONG, MPI_ANY_SOURCE, 2, MPI_COMM_WORLD, &state);
                    res.resize(num);
                    MPI_Recv(res.data(), num, MPI_UNSIGNED_LONG_LONG, state.MPI_SOURCE, 3, MPI_COMM_WORLD, &state);
                    resultFile.write((char*)res.data(), res.size() * sizeof(UInt64));
                    std::cout << "In class " << std::hex <<  funcs[state.MPI_SOURCE - 1] << " found " << std::dec << num << " functions." << std::endl;
                    funcs[state.MPI_SOURCE - 1] = i->first;
                    MPI_Send(&funcs[state.MPI_SOURCE - 1], 1, MPI_UNSIGNED_LONG_LONG, state.MPI_SOURCE, 1, MPI_COMM_WORLD);
                } else {
                    for(size_t j = 0; j < funcs.size(); j++)
                        if (funcs[j] == 0) {
                            funcs[j] = i->first;
                            MPI_Send(&funcs[j], 1, MPI_UNSIGNED_LONG_LONG, j + 1, 1, MPI_COMM_WORLD);
                            busy++;
                            break;
                        }
                }            
            }
        for(; busy != 1; busy--) {
            UInt64 num;
            std::vector<UInt64> res;
            MPI_Recv(&num, 1, MPI_UNSIGNED_LONG_LONG, MPI_ANY_SOURCE, 2, MPI_COMM_WORLD, &state);
            std::cout << "In class " << std::hex <<  funcs[state.MPI_SOURCE - 1] << " found " << std::dec << num << " functions." << std::endl;
            res.resize(num);
            MPI_Recv(res.data(), num, MPI_UNSIGNED_LONG_LONG, state.MPI_SOURCE, 3, MPI_COMM_WORLD, &state);
            resultFile.write((char*)res.data(), res.size() * sizeof(UInt64));            
            MPI_Send(&busy, 1, MPI_UNSIGNED_LONG_LONG, state.MPI_SOURCE, 2, MPI_COMM_WORLD);
        }        
        resultFile.close();
        std::cout << "Task generator ending work" << std::endl;
    } else {
        omp_set_dynamic(0);
        omp_set_num_threads(2);
        std::cout << "Starting processor: " << rank << std::endl;
        NLLibrary<CurLibInfo> nllib;
        
        filename << LIBRARYDIRECTORIES << "/nllibs/nllibrary_" << CurLibInfo::n << ".bin";
        nllib.load(filename.str());
        filename.str("");

        std::ifstream fsfile;
        std::vector<std::vector<UInt64> > fs;
        fs.resize(5);
        for(size_t i = 0; i < 5; i++) {
            UInt64 size;
            filename << LIBRARYDIRECTORIES << "/fulllibs/fulllibrary_" << (int)n << "_" << i << ".bin";
            fsfile.open(filename.str().c_str(), std::ios::in | std::ios::binary);
            filename.str("");
            fsfile.seekg(0, std::ios::end);
            size = fsfile.tellg();
            fsfile.seekg(0, std::ios::beg);
            size /= 8;
            fs[i].resize(size);
            for(size_t j = 0;  j < size; j++)
                fsfile.read((char*)&fs[i][j], sizeof(UInt64));

            fsfile.close();
        }


        while(true) {
            UInt64 lpf;
            std::vector<UInt64> res;
            res.resize(0);
            MPI_Recv(&lpf, 1, MPI_UNSIGNED_LONG_LONG, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &state);
            if (state.MPI_TAG == 1) {
                std::cout << "Processor " << std::dec << rank << " works under lpclass: " << std::hex << lpf << std::endl;
                #pragma parallel shared (nllib, res, fs)
                #pragma omp for
                for(UInt64 f = 0; f < CurLibInfo::fC; f++) {                    
                    UInt8 fs1;
                    nllib.get(f, fs1);
                    if (fs1 == f2Complication) {
                        UInt8 fs2;
                        nllib.get(f ^ lpf, fs2);
                        if (fs2 == 7) {
                            UInt64 nf = (lpf << 32) ^ f;
                            UInt64 nf1 = (nf & 0x000000FF) ^ ((nf & 0x00FF0000) >> 16);
                            UInt64 nf2 = ((nf & 0x0000FF00) >> 16) ^ ((nf & 0xFF000000) >> 32);
                            UInt8 nf1s, nf2s, nf3s;
                            nllib.get(nf1, nf1s);
                            nllib.get(nf2, nf2s);
                            nllib.get(nf1 ^ nf2, nf3s);
                            if (nf1s + nf2s >= 13 && nf1s + nf2s <= 15 &&
                                nf1s + nf3s >= 13 && nf1s + nf3s <= 15 &&
                                nf2s + nf3s >= 13 && nf2s + nf3s <= 15) {                            
                            UInt64 fsi;
                            UInt8 ts1, ts2, ts3, ts = fComplication;

                            for(size_t i = 0; i < 5 && ts == fComplication; i++) {
                                for(size_t j = 0; j < fs[i].size(); j++) {
                                    fsi = fs[i][j];
                                    nllib.get(fsi ^ lpf, ts1);
                                    nllib.get(fsi ^ f, ts2);
                                    if (ts1 + ts2 + i < ts) {
                                        ts = ts1 + ts2 + i;
                                        break;
                                    }
                                    nllib.get(fsi ^ lpf ^ f, ts3);
                                    if (ts1 + ts3 + i < ts) {
                                        ts = ts1 + ts3 + i;
                                        break;
                                    }
                                    if (ts2 + ts3 + i < ts) {
                                        ts = ts2 + ts3 + i;
                                        break;
                                    }
                                }
                            }

                            if (ts == fComplication) res.push_back((lpf << 32) ^ f);
                            }
                        }
                    }
                }
                UInt64 num = res.size();
                MPI_Send(&num, 1, MPI_UNSIGNED_LONG_LONG, 0, 2, MPI_COMM_WORLD);
                MPI_Send(res.data(), res.size(), MPI_UNSIGNED_LONG_LONG, 0, 3, MPI_COMM_WORLD);
            } else {
                std::cout << "Process " << std::dec << rank << " ending work." << std::endl;
                break;
            }
        }
    }

    MPI_Finalize();
    
    return (0);
}
